package util

import (
	"context"
	"fmt"
	"sync"

	"gitee.com/youkelike/ziam/zctl/options"
	"gitee.com/youkelike/ziam/zsdk2/rest"
	"gitee.com/youkelike/ziam/zutils/version"
	"github.com/spf13/pflag"
)

const (
	flagMatchBinaryVersion = "match-server-version"
)

// 用于检测客户端和服务端版本是否匹配
type MatchVersionFlags struct {
	Delegate options.RESTClientGetter

	RequireMatchedServerVersion bool
	checkServerVersion          sync.Once
	matchesServerVersionErr     error
}

var _ options.RESTClientGetter = &MatchVersionFlags{}

func NewMatchVersionFlags(delegate options.RESTClientGetter) *MatchVersionFlags {
	return &MatchVersionFlags{
		Delegate: delegate,
	}
}

func (f *MatchVersionFlags) ToRESTConfig() (*rest.Config, error) {
	if err := f.checkMatchingServerVersion(); err != nil {
		return nil, err
	}
	clientConfig, err := f.Delegate.ToRESTConfig()
	if err != nil {
		return nil, err
	}
	// TODO we should not have to do this.  It smacks of something going wrong.
	options.SetIAMDefaults(clientConfig)
	return clientConfig, nil
}

// 通过 zsdk2 获取服务端版本号，检查客户端和服务端版本号是否一致，用 sync.Once 限制只会检查一次
func (f *MatchVersionFlags) checkMatchingServerVersion() error {
	f.checkServerVersion.Do(func() {
		if !f.RequireMatchedServerVersion {
			return
		}

		clientConfig, err := f.Delegate.ToRESTConfig()
		if err != nil {
			f.matchesServerVersionErr = err
			return
		}

		options.SetIAMDefaults(clientConfig)
		restClient, err := rest.RESTClientFor(clientConfig)
		if err != nil {
			f.matchesServerVersionErr = err
			return
		}

		// 服务端和客户端实际使用了同一个包（zutil/version）来获取版本信息
		var sVer *version.Info
		if err := restClient.Get().AbsPath("/version").Do(context.TODO()).Into(&sVer); err != nil {
			f.matchesServerVersionErr = err
			return
		}

		clientVersion := version.Get()

		// GitVersion includes GitCommit and GitTreeState, but best to be safe?
		if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit ||
			clientVersion.GitTreeState != sVer.GitTreeState {
			f.matchesServerVersionErr = fmt.Errorf(
				"server version (%#v) differs from client version (%#v)",
				sVer,
				version.Get(),
			)
		}
	})

	return f.matchesServerVersionErr
}

func (f *MatchVersionFlags) AddFlags(flags *pflag.FlagSet) {
	flags.BoolVar(
		&f.RequireMatchedServerVersion,
		flagMatchBinaryVersion,
		f.RequireMatchedServerVersion,
		"Require server version to match client version",
	)
}
